﻿
using LightCAD.Core.Element3d;
using System;
using static System.Collections.Specialized.BitVector32;

namespace QdArch
{

    public class QdDoorAttribute : CptAttribute
    {
        public QdDoorAttribute(string category, string subCategorys) : base(category, subCategorys)
        {

        }

        public const string BuiltinUuid = "{0F3DE48D-5985-4145-BE21-831DC83A9AB1}";
        public override LcComponentDefinition GetBuiltinCpt(string subCategory)
        {
            return new QdDoorDef(BuiltinUuid, "内置", "平开门");
        }
    }

    [QdDoorAttribute("门", "平开门")]
    public class QdDoorDef : LcComponentDefinition
    {
        static QdDoorDef()
        {
            CptTypeParamsDef<QdDoorDef>.ParamsDef = new ParameterSetDefinition {
                new ParameterDefinition { DataType = LcDataType.String, Name = "MaterialId", DisplayName = "材质", },
            };
        }

        public QdDoorDef()
        {
            this.TypeParameters = new LcParameterSet(CptTypeParamsDef<QdDoorDef>.ParamsDef);
        }
        public QdDoorDef(string uuid, string name, string subCategory)
            : base(uuid, name, "门", subCategory, new LcParameterSet(CptTypeParamsDef<QdDoorDef>.ParamsDef))
        {
            this.Parameters = new ParameterSetDefinition()
            {
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Thickness", DisplayName = "厚度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Width", DisplayName = "宽度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Height", DisplayName = "高度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Bottom", DisplayName= "底高度" } ,
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsLeft", DisplayName = "是否左开", },
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsNear", DisplayName = "是否在墙体左侧", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Angle", DisplayName = "角度(0-90)", },
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsSingle", DisplayName = "单扇/双扇", },
            };

            this.Curve2dProvider = new Curve2dProvider("平开门")
            {
                GetCurve2dsFunc = (p) =>
                {
                    ListEx<Curve2d> curves = null;

                    var isSingle = p.GetValue<bool>("IsSingle");
                    if (isSingle)
                    {
                        curves = GetShape2D_单扇平开门(p);
                    }
                    else
                    {
                        curves = GetShape2D_双扇平开门(p);
                    }
                    return new Curve2dGroupCollection() { new Curve2dGroup()
                        {
                            Curve2ds=curves
                        }};
                }
            };
            var mat = MaterialManager.DefaultMat;
            if (subCategory == "平开门")
                mat = MaterialManager.GetMaterial(MaterialManager.WoodUuid);
            var handMat = MaterialManager.GetMaterial(MaterialManager.Metal1Uuid);;
            this.Solid3dProvider = new Solid3dProvider("平开门")
            {
                AssignMaterialsFunc = (c,p) => {
                    if (p.Name=="Door")
                    {
                        return new LcMaterial[] { mat };
                    }
                    return new LcMaterial[] { handMat };
                },
                GetSolid3dsFunc = (p) =>
                {
                    var solid3d = new Solid3d();
                    return GetShape3D(p);
                }
            };
        }

        private ListEx<Curve2d> GetShape2D_单扇平开门(LcParameterSet pset)
        {
            var sc = new Shape2dCreator();

            var w = (double)pset["Width"];
            //var th = (double)pset["Thickness"];
            var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];
            var angle = (double)pset["Angle"];

            var pL = sc.Point(-w / 2, 0);
            var pR = sc.Point(w / 2, 0);
            var pLNear = sc.RotatePoint(pL, pR, angle);
            var pLFar = sc.RotatePoint(pL, pR, -angle);
            var pRNear = sc.RotatePoint(pR, pL, -angle);
            var pRFar = sc.RotatePoint(pR, pL, angle);
            if (isLeft && isNear)
            {
                sc.DrawArc(pL.Clone(), w, -angle, 0);
                sc.DrawLine(pL.Clone(), pRNear.Clone());
            }
            else if (isLeft && !isNear)
            {
                sc.DrawArc(pL.Clone(), w, 0, +angle);
                sc.DrawLine(pL.Clone(), pRFar.Clone());
            }
            else if (!isLeft && isNear)
            {
                sc.DrawArc(pR.Clone(), w, 180, 180 + angle);
                sc.DrawLine(pR.Clone(), pLNear.Clone());
            }
            else if (!isLeft && !isNear)
            {
                sc.DrawArc(pR.Clone(), w, 180 - angle, 180);
                sc.DrawLine(pR.Clone(), pLFar.Clone());
            }

            return sc.Curves;
        }

        private ListEx<Curve2d> GetShape2D_双扇平开门(LcParameterSet pset)
        {
            var sc = new Shape2dCreator();

            var w = (double)pset["Width"];
            //var th = (double)pset["Thickness"];
            var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];
            var angle = (double)pset["Angle"];

            var pC = sc.Point(0, 0);
            var pL = sc.Point(-w / 2, 0);
            var pR = sc.Point(w / 2, 0);
            var pCLNear = sc.RotatePoint(pC, pL, -angle);
            var pCLFar = sc.RotatePoint(pC, pL, angle);
            var pCRNear = sc.RotatePoint(pC, pR, angle);
            var pCRFar = sc.RotatePoint(pC, pR, -angle);
            if (isNear)
            {
                sc.DrawArc(pL.Clone(), w / 2, -angle, 0);
                sc.DrawLine(pL.Clone(), pCLNear.Clone());
                sc.DrawArc(pR.Clone(), w / 2, 180, 180 + angle);
                sc.DrawLine(pR.Clone(), pCRNear.Clone());
            }
            else
            {
                sc.DrawArc(pL.Clone(), w / 2, 0, angle);
                sc.DrawLine(pL.Clone(), pCLFar.Clone());
                sc.DrawArc(pR.Clone(), w / 2, 180 - angle, 180);
                sc.DrawLine(pR.Clone(), pCRFar.Clone());
            }

            return sc.Curves;
        }


        public ListEx<Curve2d> GetShape2D_中分电梯门(LcParameterSet pset)
        {
            var sc = new Shape2dCreator();

            const double gap = 25;
            const double offset = 30;
            const double recth = 10;
            var w = (double)pset["Width"];
            var th = (double)pset["Thickness"];
            //var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];

            if (isNear)
            {
                var pL = sc.Point(-w / 2, -th / 2);
                var pR = sc.Point(w / 2, -th / 2);
                sc.DrawLine(pL.Clone(), pR.Clone());
                //left
                sc.DrawPolygon(
                    sc.Point(pL.X - offset, -th / 2 - gap),
                    sc.Point(-offset, -th / 2 - gap),
                    sc.Point(-offset, -th / 2 - gap - recth),
                    sc.Point(pL.X - offset, -th / 2 - gap - recth));
                //right
                sc.DrawPolygon(
                    sc.Point(pR.X + offset, -th / 2 - gap),
                    sc.Point(offset, -th / 2 - gap),
                    sc.Point(offset, -th / 2 - gap - recth),
                    sc.Point(pR.X + offset, -th / 2 - gap - recth));
            }
            else
            {
                var pL = sc.Point(-w / 2, th / 2);
                var pR = sc.Point(w / 2, th / 2);
                sc.DrawLine(pL.Clone(), pR.Clone());
                //left
                sc.DrawPolygon(
                    sc.Point(pL.X - offset, th / 2 + gap),
                    sc.Point(-offset, th / 2 + gap),
                    sc.Point(-offset, th / 2 + gap + recth),
                    sc.Point(pL.X - offset, th / 2 + gap + recth));
                //right
                sc.DrawPolygon(
                    sc.Point(pR.X + offset, th / 2 + gap),
                    sc.Point(offset, th / 2 + gap),
                    sc.Point(offset, th / 2 + gap + recth),
                    sc.Point(pR.X + offset, th / 2 + gap + recth));

            }

            return sc.Curves;
        }

        private Solid3dCollection GetShape3D(LcParameterSet pset)
        {
            var isLeft = (bool)pset["IsLeft"];
            var bottom = (double)pset["Bottom"];
            var width = (double)pset["Width"];
            var start = new Vector3(-width / 2, 0, bottom);
            var end = new Vector3(width / 2, 0, bottom);
            var height = (double)pset["Height"];
            var thickness = (double)pset["Thickness"];
            var thicknessHlf = thickness / 2;
            var lineVec = new Vector3().SubVectors(end, start);
            var len = lineVec.Length();
            var xAxis = lineVec.Clone().Normalize();
            var yAxis = VectorUtil.ZAxis.Clone();
            var zAxis = new Vector3().CrossVectors(xAxis, yAxis).Normalize();
            var coodMat = new Matrix4();
            coodMat.MakeBasis(xAxis, yAxis, zAxis);
            coodMat.SetPosition(start);
            var shape = ShapeUtil.Rect(0, 0, len, height);
            var handleLoc = isLeft ? len - 100 : 100;
            var handleShape = ShapeUtil.Hexagon(handleLoc, height / 2, 50);
            var doorGeoData = GeoUtil.GetStretchGeometryData(shape, coodMat, thicknessHlf, -thicknessHlf);
            var handleGeoData = GeoUtil.GetStretchGeometryData(handleShape, coodMat, thicknessHlf + 50, -thicknessHlf - 50);
            //var geoDoor = doorGeoData.GetBufferGeometry();
            //geoDoor.computeVertexNormals();
            //var geoHandle = handleGeoData.GetBufferGeometry();
            //geoHandle.computeVertexNormals();

            var solids = new Solid3dCollection()
            {
                new Solid3d()
                {
                    Name="Door",
                    Geometry = new LightCAD.MathLib.GeometryData()
                    {
                        Verteics = doorGeoData.Position.ToArray(),
                        Indics = doorGeoData.Index.ToArray(),
                        Groups = new GeometryGroup[1]
                        {
                            new GeometryGroup{ Name = "Geometry", Start = 0, Count = doorGeoData.Index.Length, MaterialIndex = 0 },
                        }
                    }
                },
                new Solid3d()
                {
                    Name="Hand",
                    Geometry = new LightCAD.MathLib.GeometryData()
                    {
                        Verteics = handleGeoData.Position.ToArray(),
                        Indics = handleGeoData.Index.ToArray(),
                        Groups = new GeometryGroup[1]
                        {
                            new GeometryGroup{ Name = "Geometry", Start = 0, Count = handleGeoData.Index.Length, MaterialIndex = 0 },
                        }
                    }
                }
            };
            return solids;
        }
    }

    public class QdDoorInstance : LcComponentInstance
    {
        public QdDoorDef DoorDef { get => this.Definition as QdDoorDef; }

        /// <summary>
        /// 在墙上用中心插入
        /// </summary>
        public QdWall Wall { get; set; }

        private Vector2 startPoint = null;
        public Vector2 StartPoint
        {
            get
            {
                if (startPoint == null)
                {
                    var wallLineDir = this.Wall.BaseLine.Dir;
                    var offset = wallLineDir * (this.Width / 2);
                    startPoint = this.Position.ToVector2() - offset;
                }
                return startPoint;
            }
        }
        public Vector2 endPoint = null;
        public Vector2 EndPoint
        {
            get
            {
                if (endPoint == null)
                {
                    var wallLineDir = this.Wall.BaseLine.Dir;
                    var offset = wallLineDir * (this.Width / 2);
                    endPoint = this.Position.ToVector2() + offset;
                }
                return endPoint;
            }
        }
        public Vector2 controlPoint = null;
        public Vector2 ControlPoint
        {
            get
            {
                if (controlPoint == null)
                {
                    var isRight = !this.IsLeft; //是否在插入点右侧
                    var isUp = !this.IsNear; //是否在墙线上方，即墙线左侧

                    //第三个点的旋转角度， 经观察发现三个点在半径相同的同一个圆上，圆心为doorRef.InsertPoint
                    double angle = this.Wall.BaseLine.Dir.Angle();
                    double angle45 = Math.PI / 4;
                    if (isRight && isUp)
                    {
                        angle += angle45;
                    }
                    else if (!isRight && isUp)
                    {
                        angle += Math.PI / 2 + angle45;
                    }
                    else if (!isRight && !isUp)
                    {
                        angle += Math.PI + angle45;
                    }
                    else if (isRight && !isUp)
                    {
                        angle += Math.PI * 1.5 + angle45;
                    }

                    var lineDir = Vector2.RotateInRadian(new Vector2(1, 0), angle);
                    var lineOffset = lineDir * (this.Width / 2);

                    controlPoint =  this.Position.ToVector2() + lineOffset;
                }
                return controlPoint;

            }
        }

        public double Thickness
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Thickness"]);
            }
            set
            {
                this.Parameters["Thickness"] = value;
            }
        }
        public double Width
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Width"]);
            }
            set
            {
                this.Parameters["Width"] = value;
            }
        }
        public double Height
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Height"]);
            }
            set
            {
                this.Parameters["Height"] = value;
            }
        }
        public double Bottom
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Bottom"]);
            }
            set
            {
                this.Parameters["Bottom"] = value;
            }
        }

        public bool IsLeft
        {
            get
            {
                return Convert.ToBoolean(this.Parameters["IsLeft"]);
            }
            set
            {
                this.Parameters["IsLeft"] = value;
            }
        }
        public bool IsNear
        {
            get
            {
                return Convert.ToBoolean(this.Parameters["IsNear"]);
            }
            set
            {
                this.Parameters["IsNear"] = value;
            }
        }

        public QdDoorInstance(QdDoorDef doorRef) : base(doorRef)
        {
            this.Type = ArchElementType.Door;
        }

        public QdDoorInstance(double thickness, double width, double height, double bottom, bool isLeft, bool isNear, double angle, bool isSingle, QdDoorDef doorRef) : this(doorRef)
        {
            this.Parameters.SetValue("Thickness", thickness);
            this.Parameters.SetValue("Width", width);
            this.Parameters.SetValue("Height", height);
            this.Parameters.SetValue("Bottom", bottom);
            this.Parameters.SetValue("IsLeft", isLeft);
            this.Parameters.SetValue("IsNear", isNear);
            this.Parameters.SetValue("Angle", angle);
            this.Parameters.SetValue("IsSingle", isSingle);
        }

        public override Box2 GetBoundingBox()
        {
            if (this.Parameters == null || this.Parameters.Values.Length == 0)
            {
                return Box2.Empty;
            }

            var center = this.Position.ToVector2() - new Vector2(Width, Width);
            return new Box2(center, Width * 2, Width * 2);

        }

        public override void ResetCache()
        {
            base.ResetCache();
            this.startPoint = null;
            this.endPoint = null;
            this.controlPoint = null;

            this.Wall.UpdateLcComponent(this, nameof(QdDoorInstance), AssociateType.Cross);
        }

        public override void OnRemoveAfter()
        {
            this.Wall.RemoveLcComponent(this, nameof(QdDoorInstance), AssociateType.Cross);
        }

        public override void Mirror(Vector2 axisStart, Vector2 axisEnd)
        {
            return;
        }

        public override LcElement Clone()
        {
            var clone = new QdDoorInstance(this.DoorDef);
            clone.Copy(this);
            clone.Initilize(this.Document);
            return clone;
        }

        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var doorRef = (QdDoorInstance)src;
            this.Wall = doorRef.Wall;
        }

        public override void ReadObjectRefs<T>(ICollection<T> objs)
        {
            if (this.Wall == null)
            {
                var wallId = (long)this.UserData["WallId"];
                this.Wall = objs.FirstOrDefault(o => o.Id == wallId) as QdWall;
                if (this.DoorDef != null)
                {
                    this.Wall.AddLcComponent(this, nameof(QdDoorInstance), AssociateType.Cross);
                }
            }
        }

        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            base.WriteProperties(writer, soptions);
            writer.WriteNumberProperty("WallId", this.Wall.Id);
        }

        public override void ReadProperties(ref JsonElement jele)
        {
            base.ReadProperties(ref jele);
            if (this.UserData == null)
            {
                this.UserData = new Dictionary<string, object>();
            }
            this.UserData["WallId"] = jele.ReadIdProperty("WallId");
        }
    }
}